using System.Collections.Concurrent;
using Certify.Models;
using Certify.Models.Hub;
using Certify.Models.Reporting;

namespace Certify.Server.Hub.Api.SignalR.ManagementHub
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
    public interface IInstanceManagementStateProvider
    {
        void Clear();
        void SetManagementHubInstanceId(string instanceId);
        string GetManagementHubInstanceId();
        void UpdateInstanceConnectionInfo(string connectionId, ManagedInstanceInfo instanceInfo);
        void UpdateInstanceStatusSummary(string instanceId, StatusSummary summary);
        bool HasLastUpdateIdChanged(string instanceId, long newLastUpdateId);
        string GetConnectionIdForInstance(string instanceId);
        string? GetInstanceIdForConnection(string connectionId);
        List<ManagedInstanceInfo> GetConnectedInstances();
        void AddAwaitedCommandRequest(InstanceCommandRequest command);

        /// <summary>
        /// Remove an awaited command so we don't keep waiting on the result
        /// </summary>
        /// <param name="commandId"></param>
        void RemoveAwaitedCommandRequest(Guid commandId);
        InstanceCommandRequest? GetAwaitedCommandRequest(Guid commandId);
        void AddAwaitedCommandResult(InstanceCommandResult result);

        /// <summary>
        /// Wait for a command result to be available
        /// </summary>
        /// <returns></returns>
        Task<InstanceCommandResult?> ConsumeAwaitedCommandResult(InstanceCommandRequest cmd);
        void UpdateInstanceItemInfo(string instanceId, List<ManagedCertificate> items);
        IEnumerable<string> GetInstancesDueRefresh(int minutes);
        ConcurrentDictionary<string, ManagedInstanceItems> GetManagedInstanceItems(string? instanceId = null);
        void UpdateCachedManagedInstanceItem(string instanceId, ManagedCertificate managedCertificate);
        void DeleteCachedManagedInstanceItem(string instanceId, string managedCertificateId);
        bool HasItemsForManagedInstance(string instanceId);

        bool HasStatusSummaryForManagedInstance(string instanceId);
        ConcurrentDictionary<string, StatusSummary> GetManagedInstanceStatusSummaries();
        StatusSummary? GetManagedInstanceStatusSummary(string instanceId);
        void UpdateInstanceConnectionStatus(string instanceId, string status);

        void AddOrUpdateSystemStatusItem(ActionStep status);
        List<ActionStep> GetSystemStatusItems();
    }
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

    /// <summary>
    /// Track state across pool of instance connections to the management hub
    /// </summary>
    public class InstanceManagementStateProvider : IInstanceManagementStateProvider
    {
        private ConcurrentDictionary<string, ManagedInstanceInfo> _instanceConnections = [];
        private ConcurrentDictionary<Guid, InstanceCommandRequest> _awaitedCommandRequests = [];
        private ConcurrentDictionary<Guid, InstanceCommandResult> _awaitedCommandResults = [];

        private ConcurrentDictionary<string, ManagedInstanceItems> _managedInstanceItems = [];
        private ConcurrentDictionary<string, StatusSummary> _managedInstanceStatusSummary = [];

        private List<ActionStep> _systemStatusItems = [];

        private ILogger<InstanceManagementStateProvider> _logger;
        private string _mgmtHubInstanceId = string.Empty;

        /// <summary>
        /// Create a new instance of the state provider
        /// </summary>
        /// <param name="logger"></param>
        public InstanceManagementStateProvider(ILogger<InstanceManagementStateProvider> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// Clear all state
        /// </summary>
        public void Clear()
        {
            _logger.LogWarning("Flushing management hub state, clients will need to reconnect.");
            _instanceConnections.Clear();
            _managedInstanceItems.Clear();
            _awaitedCommandRequests.Clear();
            _awaitedCommandResults.Clear();
            _managedInstanceStatusSummary.Clear();

        }

        /// <summary>
        /// Set the instance ID of the management hub
        /// </summary>
        /// <param name="instanceId"></param>
        public void SetManagementHubInstanceId(string instanceId)
        {
            _mgmtHubInstanceId = instanceId;
        }

        /// <summary>
        /// Get the instance ID of the management hub
        /// </summary>
        /// <returns></returns>
        public string GetManagementHubInstanceId()
        {
            return _mgmtHubInstanceId;
        }

        /// <summary>
        /// Get a list of all connected instances
        /// </summary>
        /// <returns></returns>
        public List<ManagedInstanceInfo> GetConnectedInstances()
        {
            return _instanceConnections.Values.ToList();
        }

        /// <summary>
        /// Track the instance info associated with a hub connection
        /// </summary>
        /// <param name="connectionId"></param>
        /// <param name="instanceInfo"></param>
        public void UpdateInstanceConnectionInfo(string connectionId, ManagedInstanceInfo instanceInfo)
        {
            var existingOther = _instanceConnections.FirstOrDefault(a => a.Value.InstanceId == instanceInfo.InstanceId && a.Key != connectionId);

            if (existingOther.Value != null)
            {
                _logger.LogWarning("[InstanceManagementStateProvider] Connection ID for instance {instance} changed to {connection}", instanceInfo.Title, connectionId);
                _instanceConnections.Remove(existingOther.Key, out _);
            }

            _instanceConnections.AddOrUpdate(connectionId, instanceInfo, (i, oldValue) =>
            {
                instanceInfo.ConnectionStatus = ConnectionStatus.Connected;
                return instanceInfo;
            });
        }

        /// <summary>
        /// Update the status summary for a given instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="summary"></param>
        public void UpdateInstanceStatusSummary(string instanceId, StatusSummary summary)
        {
            if (summary == null)
            {
                // we don't store null summary updates, keep latest if any
                return;
            }

            var previousSummary = _managedInstanceStatusSummary.TryGetValue(instanceId, out var existing) ? existing : null;

            _managedInstanceStatusSummary.AddOrUpdate(instanceId, summary, (i, oldValue) => summary);

            // Cleanup any mismatched entries where the key doesn't match the summary's InstanceId, these can appear if instance id was temporary while verifying connection
            var keysToRemove = _managedInstanceStatusSummary
                .Where(kvp => kvp.Key != kvp.Value.InstanceId)
                .Select(kvp => kvp.Key)
                .ToList();

            if (keysToRemove.Count > 0)
            {
                _logger.LogInformation("Cleaning up {count} mismatched managed instance status summary entries.", keysToRemove.Count);
            }

            foreach (var key in keysToRemove)
            {
                _managedInstanceStatusSummary.TryRemove(key, out _);
            }
        }

        /// <summary>
        /// Check if the LastUpdateId has changed for a given instance, indicating managed items have been added/updated/deleted
        /// </summary>
        /// <param name="instanceId">The instance to check</param>
        /// <param name="newLastUpdateId">The new LastUpdateId from the status summary</param>
        /// <returns>True if the LastUpdateId has changed (or if this is the first status summary), false otherwise</returns>
        public bool HasLastUpdateIdChanged(string instanceId, long newLastUpdateId)
        {
            if (!_managedInstanceStatusSummary.TryGetValue(instanceId, out var currentSummary))
            {
                // No existing summary, this is a new instance or first report
                return true;
            }

            // Check if the LastUpdateId has changed
            var hasChanged = currentSummary.LastUpdateId != newLastUpdateId;

            if (hasChanged)
            {
                _logger.LogDebug("[HasLastUpdateIdChanged] Instance {instanceId} LastUpdateId changed from {oldId} to {newId}",
                    instanceId, currentSummary.LastUpdateId, newLastUpdateId);
            }

            return hasChanged;
        }

        /// <summary>
        /// Get the current connection ID we haev associated with the given instance id
        /// </summary>
        /// <param name="instanceId"></param>
        /// <returns></returns>
        public string GetConnectionIdForInstance(string instanceId)
        {
            // TODO: of instances use the same instanceid accidentally they will clobber each other
            var info = _instanceConnections.FirstOrDefault(k => k.Value.InstanceId == instanceId);

            return info.Key;
        }

        /// <summary>
        /// Get the instance ID associated with a given connection ID
        /// </summary>
        /// <param name="connectionId"></param>
        /// <returns></returns>
        public string? GetInstanceIdForConnection(string connectionId)
        {
            if (connectionId == _mgmtHubInstanceId)
            {
                // directly connected instance
                return _mgmtHubInstanceId;
            }

            _instanceConnections.TryGetValue(connectionId, out var managedInstanceInfo);

            if (managedInstanceInfo != null)
            {
                return managedInstanceInfo.InstanceId;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Track command requests we are waiting on responses for.
        /// </summary>
        /// <param name="command"></param>
        public void AddAwaitedCommandRequest(InstanceCommandRequest command)
        {
            _logger.LogDebug("[AddAwaitedCommandRequest] Issued command {commandId} {cmdType}.", command.CommandId, command.CommandType);

            _awaitedCommandRequests.AddOrUpdate(command.CommandId, command, (i, oldValue) => { return command; });
        }

        /// <summary>
        /// Get command request we are waiting on a response for
        /// </summary>
        /// <param name="commandId"></param>
        /// <returns></returns>
        public InstanceCommandRequest? GetAwaitedCommandRequest(Guid commandId)
        {
            _awaitedCommandRequests.TryGetValue(commandId, out var cmd);
            return cmd;
        }

        /// <summary>
        /// Add a command result we are waiting for
        /// </summary>
        /// <param name="result"></param>
        public void AddAwaitedCommandResult(InstanceCommandResult result)
        {
            _logger.LogDebug("[AddAwaitedCommandResult] {commandId} {cmdType}.", result.CommandId, result.CommandType);
            _awaitedCommandResults.AddOrUpdate(result.CommandId, result, (i, oldValue) => result);
        }

        /// <summary>
        /// Wait for a command result to be available
        /// </summary>
        /// <param name="cmd"></param>
        /// <returns></returns>
        public async Task<InstanceCommandResult?> ConsumeAwaitedCommandResult(InstanceCommandRequest cmd)
        {
            _logger.LogDebug("[ConsumeAwaitedCommandResult] Waiting for command result {commandId}..", cmd.CommandId);
            var attempts = 50;

            while (attempts > 0 && !_awaitedCommandResults.TryGetValue(cmd.CommandId, out _))
            {
                attempts--;
                await Task.Delay(100);
            }

            _awaitedCommandResults.Remove(cmd.CommandId, out var cmdResult);

            if (cmdResult == null)
            {
                _logger.LogDebug("[ConsumeAwaitedCommandResult] Gave up waiting for command result {commandId} {cmdType}..", cmd.CommandId, cmd.CommandType);
            }
            else
            {
                _logger.LogDebug("[ConsumeAwaitedCommandResult] Received and consumed command result {commandId} {cmdType}..", cmd.CommandId, cmd.CommandType);
            }

            RemoveAwaitedCommandRequest(cmd.CommandId);

            return cmdResult;
        }

        /// <summary>
        /// Remove a command request we have received a response for
        /// </summary>
        /// <param name="commandId"></param>
        public void RemoveAwaitedCommandRequest(Guid commandId)
        {
            _awaitedCommandRequests.Remove(commandId, out var request);

            if (request != null)
            {
                _logger.LogDebug("[RemoveAwaitedCommandRequest] Removed awaited command request {commandId} {cmdType}..", request.CommandId, request.CommandType);
            }
        }

        /// <summary>
        /// Update the managed certificate items for a given instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="items"></param>
        public void UpdateInstanceItemInfo(string instanceId, List<ManagedCertificate> items)
        {
            var info = new ManagedInstanceItems { InstanceId = instanceId, Items = items, LastRefreshed = DateTimeOffset.UtcNow };
            _managedInstanceItems.AddOrUpdate(instanceId, info, (k, old) => info);
        }

        /// <summary>
        /// Get a list of instance IDs which are due a refresh based on last refreshed time
        /// </summary>
        /// <param name="minutes"></param>
        /// <returns></returns>
        public IEnumerable<string> GetInstancesDueRefresh(int minutes)
        {
            // return list of instance IDs which are due a refresh based on last refreshed time
            var dueInstances = _managedInstanceItems
                .Where(kvp => kvp.Value.LastRefreshed == null || kvp.Value.LastRefreshed <= DateTimeOffset.UtcNow.AddMinutes(-minutes))
                .Select(kvp => kvp.Key);

            return dueInstances;
        }

        /// <summary>
        /// Get the current managed certificate items for a given instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <returns></returns>
        public ConcurrentDictionary<string, ManagedInstanceItems> GetManagedInstanceItems(string? instanceId = null)
        {
            return _managedInstanceItems;
        }

        /// <summary>
        /// Get the current status summaries for all managed instances
        /// </summary>
        /// <returns></returns>
        public ConcurrentDictionary<string, StatusSummary> GetManagedInstanceStatusSummaries()
        {
            return _managedInstanceStatusSummary;
        }

        /// <summary>
        /// Retrieves the status summary of a managed instance based on its identifier.
        /// </summary>
        /// <param name="instanceId">The identifier used to locate the specific managed instance's status summary.</param>
        /// <returns>Returns the status summary associated with the specified managed instance, or null if not found.</returns>
        public StatusSummary? GetManagedInstanceStatusSummary(string instanceId)
        {
            return _managedInstanceStatusSummary.FirstOrDefault(s => s.Key == instanceId).Value;
        }

        /// <summary>
        /// Update a cached managed certificate item for a given instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="managedCertificate"></param>
        public void UpdateCachedManagedInstanceItem(string instanceId, ManagedCertificate managedCertificate)
        {
            if (_managedInstanceItems.TryGetValue(instanceId, out var instance))
            {
                if (instance.Items != null)
                {
                    // replace item in the list at the same position

                    var original = instance.Items.FirstOrDefault(i => i.Id == managedCertificate.Id);
                    if (original != null)
                    {
                        var idx = instance.Items.IndexOf(original);
                        instance.Items[idx] = managedCertificate;
                    }
                    else
                    {
                        instance.Items.Add(managedCertificate);
                    }
                }
            }
        }

        /// <summary>
        /// Check if we have any items cached for a given managed instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <returns></returns>
        public bool HasItemsForManagedInstance(string instanceId)
        {
            return _managedInstanceItems.ContainsKey(instanceId);
        }

        /// <summary>
        /// Check if we have a status summary for a given managed instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <returns></returns>
        public bool HasStatusSummaryForManagedInstance(string instanceId)
        {
            return _managedInstanceStatusSummary.ContainsKey(instanceId);
        }

        /// <summary>
        /// Remove a cached managed certificate item for a given instance
        /// </summary>
        /// <param name="instanceId"></param>
        /// <param name="managedCertificateId"></param>
        public void DeleteCachedManagedInstanceItem(string instanceId, string managedCertificateId)
        {
            if (_managedInstanceItems.TryGetValue(instanceId, out var instance))
            {
                instance.Items.RemoveAll(r => r.Id == managedCertificateId);
            }
        }

        /// <summary>  
        /// Update the connection status for a given instance.  
        /// </summary>  
        /// <param name="instanceId">The ID of the instance.</param>  
        /// <param name="status">The new connection status.</param>  
        public void UpdateInstanceConnectionStatus(string instanceId, string status)
        {
            var info = _instanceConnections.FirstOrDefault(k => k.Value.InstanceId == instanceId);
            if (info.Value != null)
            {
                info.Value.ConnectionStatus = status;
                UpdateInstanceConnectionInfo(info.Key, info.Value);
            }
        }

        /// <summary>
        /// Adds a new system status item or updates an existing one in the collection.
        /// </summary>
        /// <param name="status">Represents the action step to be added or updated in the system status items.</param>
        public void AddOrUpdateSystemStatusItem(ActionStep status)
        {
            var existing = _systemStatusItems.FirstOrDefault(k => k.Key == status.Key);

            if (existing != null)
            {
                _systemStatusItems.Remove(existing);
            }

            _systemStatusItems.Add(status);
        }

        /// <summary>
        /// Retrieves a list of action steps representing the current system status.
        /// </summary>
        /// <returns>Returns a List of ActionStep objects.</returns>
        public List<ActionStep> GetSystemStatusItems()
        {
            return _systemStatusItems.ToList();
        }
    }
}
